`include "../include/cpu_defines.sv"
module PRF(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic stall,
	input logic inst1_en,
	input logic inst2_en,
	input logic [5: 0] inst1_rs,
	input logic [5: 0] inst1_rt,
	input logic [5: 0] inst1_rd,
	input logic [5: 0] inst2_rs,
	input logic [5: 0] inst2_rt,
	input logic [5: 0] inst2_rd,
	input logic [5: 0] search1,
	input logic [5: 0] search2,
	input logic [5: 0] search3,
	input logic [5: 0] alu_rs1,
	input logic [5: 0] alu_rt1,
	input logic [5: 0] alu_rs2,
	input logic [5: 0] alu_rt2,
	input logic [5 :0] mult_rs,
	input logic [5: 0] mult_rt,
	input logic [5: 0] cp0_rt,
	input logic rob_en1,
	input logic rob_en2,
	input logic [5: 0] rob_origin_rd1,
	input logic [5: 0] rob_origin_rd2,
	input logic [5: 0] rob_rd1,
	input logic [5: 0] rob_rd2,
	input logic wb_en1,
	input logic wb_en2,
	input logic wb_en3,
	input logic [5: 0] wb_rd1,
	input logic [5: 0] wb_rd2,
	input logic [5: 0] wb_rd3,
	input logic [31: 0] wb_data1,
	input logic [31: 0] wb_data2,
	input logic [31: 0] wb_data3,
	input logic branch_fail1,
	input logic branch_fail2,
	output logic [5: 0] out_inst1_rs,
	output logic [5: 0] out_inst1_rt,
	output logic [5: 0] out_inst1_rd,
	output logic [5: 0] out_inst2_rs,
	output logic [5: 0] out_inst2_rt,
	output logic [5: 0] out_inst2_rd,
	output logic inst1_src1_en,
	output logic inst1_src2_en,
	output logic inst2_src1_en,
	output logic inst2_src2_en,
	output logic [31: 0] out_data1,
	output logic [31: 0] out_data2,
	output logic [31: 0] out_data3,
	output logic [31: 0] alu_src1,
	output logic [31: 0] alu_src2,
	output logic [31 :0] alu_src3,
	output logic [31 :0 ]alu_src4,
	output logic [31: 0] mult_src1,
	output logic [31: 0] mult_src2,
	output logic [31: 0] cp0_data,
	output logic flush_request
);
	(* keep="true" *)
	logic [31: 0] prf[`PRF_SIZE-1: 0];
	(* keep="true" *)
	logic [31: 0] mem_prf[`PRF_SIZE-1: 0];
	// logic [5: 0] free_list[`PRF_SIZE-`REG_NUM-1: 0];
	logic [5: 0] head, tail, next_head, next_tail, tailp1, headp1;

	logic rs_raw, rt_raw, raw, waw;
	logic [31: 0] prf_out1, prf_out2, prf_out3, prf_out4;

	logic inst1_rd_useful, inst2_rd_useful;
	logic [1: 0] request_num, retire_num;
	logic [5: 0] remain_num;
	logic [6: 0] next_remain_num;
	logic availiable;
	logic rob1_free_availiable, rob2_free_availiable;
	logic wbwr_en1, wbwr_en2, wbwr_en3;  
	logic [5: 0] rob1_free_index, rob2_free_index;
	logic [63: 0] wb_rd1_decode, wb_rd2_decode, wb_rd3_decode, retire_origin_rd1_decode, retire_origin_rd2_decode;
	logic [63: 0] free_list, retire_free_list, busy_list;

	logic [5: 0] prf_inst1_rs, prf_inst1_rt, prf_inst2_rs, prf_inst2_rt;
	logic[5: 0] rat1_inst1_rs, rat1_inst1_rt, rat1_inst2_rs, rat1_inst2_rt, rat1_rob_rd1, rat1_rob_rd2;
	logic[5: 0] rat2_inst1_rs, rat2_inst1_rt, rat2_inst2_rs, rat2_inst2_rt, rat2_rob_rd1, rat2_rob_rd2;
	// logic inst1_en1, inst1_en2, inst2_en1, inst2_en2;
	logic [5: 0] wb_rat_rd1, wb_rat_rd2;

	(* RAM_STYLE="distributed" *)
	logic [`REG_NUM-1: 0][5: 0]  rob_rat;
	logic next_fail;

	assign inst1_rd_useful = inst1_en && inst1_rd != 6'b0;
	assign inst2_rd_useful = inst2_en && inst2_rd != 6'b0;
	assign request_num = inst1_rd_useful + inst2_rd_useful;
	// assign next_head = head + request_num > `PRF_SIZE - `REG_NUM - 1 ? head+request_num-(`PRF_SIZE - `REG_NUM) : head + request_num;
	// assign next_tail = tail + retire_num > `PRF_SIZE - `REG_NUM - 1 ? tail + retire_num - (`PRF_SIZE - `REG_NUM) : tail + retire_num;
	// assign tailp1 = tail + 1 > `PRF_SIZE - `REG_NUM - 1 ? tail + 1 - (`PRF_SIZE - `REG_NUM) : tail + 1;
	// assign headp1 = head + 1 > `PRF_SIZE - `REG_NUM - 1 ? head + 1 - (`PRF_SIZE - `REG_NUM) : head + 1;
	assign wbwr_en1 = wb_en1 && wb_rd1 != 0;
	assign wbwr_en2 = wb_en2 && wb_rd2 != 0;
	assign wbwr_en3 = wb_en3 && wb_rd3 != 0;

	assign rob1_free_availiable = rob_en1 & rob_rd1 != 0;
	assign rob2_free_availiable = rob_en2 & rob_rd2 != 0 && !branch_fail1;
	// assign rob1_free_index = rat1_rob_rd1 == rat2_rob_rd1 ? rob_rd1 : rat2_rob_rd1;
	// assign rob2_free_index = rat1_rob_rd2 == rat2_rob_rd2 ? rob_rd2 : rat2_rob_rd2;

	// if waw && (!raw) request_num is 1, but in the bottom is 0
	// assign request_num[1] = inst1_rd_useful & inst2_rd_useful & ((~waw) | (waw & (~raw)));
	// assign request_num[0] = inst1_rd_useful ^ inst2_rd_useful;
	assign retire_num[1] = rob1_free_availiable & rob2_free_availiable;
	assign retire_num[0] = rob1_free_availiable ^ rob2_free_availiable;
	assign next_remain_num = remain_num + retire_num - ({2{~stall}} & request_num);
	assign availiable = remain_num >= request_num;

	logic [63: 0] reverse_free_list, free_index1_expand, free_index2_expand;
	logic [5: 0] free_index1, free_index2;
	logic [5: 0] retire_index1, retire_index2;

	
	priority_selector64 selector_free_index1(free_list, free_index1_expand);
	priority_selector64_reverse selector_free_index2(free_list, free_index2_expand);
	encoder_64to6 encoder_free_index1(free_index1_expand, free_index1);
	encoder_64to6 encoder_free_index2(free_index2_expand, free_index2);
	assign retire_index1 = rob_rat[rob_origin_rd1];
	assign retire_index2 = rob1_free_availiable && rob2_free_availiable && (rob_origin_rd1 == rob_origin_rd2) ? rob_rd1 : rob_rat[rob_origin_rd2];


	assign rs_raw = inst1_rd == inst2_rs;
	assign rt_raw = inst1_rd == inst2_rt;
	assign raw = rs_raw | rt_raw;
	assign waw = inst1_rd == inst2_rd;

	decoder_6to64 decoder_wb_rd1((wb_rd1 ), wbwr_en1, wb_rd1_decode);
	decoder_6to64 decoder_wb_rd2((wb_rd2 ), wbwr_en2, wb_rd2_decode);
	decoder_6to64 decoder_wb_rd3((wb_rd3 ), wbwr_en3, wb_rd3_decode);
	decoder_6to64 decoder_retire_origin_rd1(rob_origin_rd1, rob1_free_availiable, retire_origin_rd1_decode);
	decoder_6to64 decoder_retire_origin_rd2(rob_origin_rd2, rob2_free_availiable, retire_origin_rd2_decode);

	assign out_data1 = mem_prf[search1];
	assign out_data2 = mem_prf[search2];
	assign out_data3 = mem_prf[search3];
	assign mult_src1 = mem_prf[mult_rs];
	assign mult_src2 = mem_prf[mult_rt];
	assign alu_src1 = prf[alu_rs1];
	assign alu_src2 = prf[alu_rt1];
	assign alu_src3 = prf[alu_rs2];
	assign alu_src4 = prf[alu_rt2];
	assign cp0_data = prf[cp0_rt];



	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0)begin
			remain_num <= `PRF_SIZE - `REG_NUM;
			free_list <= 64'hfffffffc00000000;
			retire_free_list <= 64'hfffffffc00000000;
			busy_list <= 64'hffffffff_ffffffff;
		end
		else if(next_fail)begin
			free_list <= retire_free_list;
			busy_list <= 64'hffffffff_ffffffff;
			remain_num <= `PRF_SIZE - `REG_NUM;
		end
		else begin
			// tail <= next_tail;
			remain_num <= next_remain_num;
			free_list <= (free_list | 
			({64{rob1_free_availiable}} & (64'b1 << retire_index1)) | 
			({64{rob2_free_availiable}} & (64'b1 << retire_index2))) & 
			~({64{~stall & inst1_rd_useful}} & free_index1_expand) & 
			~({64{~stall & inst2_rd_useful}} & ((!inst1_rd_useful ? free_index1_expand : free_index2_expand)));
			retire_free_list <= (retire_free_list | 
			({64{rob1_free_availiable}} & (64'b1 << retire_index1)) | 
			({64{rob2_free_availiable}} & (64'b1 << retire_index2))) & 
			~({64{rob2_free_availiable}} & (64'b1 << rob_rd2)) & 
			~({64{rob1_free_availiable & ~(rob1_free_availiable & rob2_free_availiable & (rob_origin_rd1 == rob_origin_rd2))}} & (64'b1 << rob_rd1));
			busy_list <= (busy_list | 
			{64{wbwr_en1}} & (64'b1 << wb_rd1) | 
			{64{wbwr_en2}} & (64'b1 << wb_rd2) |
			{64{wbwr_en3}} & (64'b1 << wb_rd3)) & 
			(~({64{~stall & inst1_rd_useful}} & free_index1_expand)) & 
			(~({64{~stall & inst2_rd_useful}} & (!inst1_rd_useful ? free_index1_expand : free_index2_expand))); 
			// if(!stall)begin
			// 	head <= next_head;
			// end
		end
	end

	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0)begin
			for(int i=0; i<`PRF_SIZE; i++)begin
				prf[i] <= 0;
				mem_prf[i] <= 0;
			end
			inst1_src1_en <= 0;
			inst2_src1_en <= 0;
			inst1_src2_en <= 0;
			inst2_src2_en <= 0;
		end
		else begin
			if(!stall)begin
				if(inst1_rs != 0 && (busy_list[out_inst1_rs] || out_inst1_rs == wb_rd1 || out_inst1_rs == wb_rd2 || out_inst1_rs == wb_rd3))begin
					inst1_src1_en <= 1'b1;
				end
				else begin
					inst1_src1_en <= 1'b0;
				end
				if(inst1_rt != 0 && (busy_list[out_inst1_rt] || out_inst1_rt == wb_rd1 || out_inst1_rt == wb_rd2 || out_inst1_rt == wb_rd3))begin
					inst1_src2_en <= 1'b1;
				end
				else begin
					inst1_src2_en <= 1'b0;
				end

				if(inst2_rs != 0 && !rs_raw && (busy_list[out_inst2_rs] || out_inst2_rs == wb_rd1 || out_inst2_rs == wb_rd2 || out_inst2_rs == wb_rd3))begin
					inst2_src1_en <= 1'b1;
				end
				else begin
					inst2_src1_en <= 1'b0;
				end
				if(inst2_rt != 0 && !rt_raw && (busy_list[out_inst2_rt] || out_inst2_rt == wb_rd1 || out_inst2_rt == wb_rd2 || out_inst2_rt == wb_rd3))begin
					inst2_src2_en <= 1'b1;
				end
				else begin
					inst2_src2_en <= 1'b0;
				end
			end
			else begin
				inst1_src1_en <= 0;
				inst2_src1_en <= 0;
				inst1_src2_en <= 0;
				inst2_src2_en <= 0;
			end
			for(int i=0; i<`PRF_SIZE; i++)begin
				if(wb_rd1_decode[i] || wb_rd2_decode[i] || wb_rd3_decode[i])begin
					prf[i] <= {32{wb_rd1_decode[i]}} & wb_data1 | {32{wb_rd2_decode[i]}} & wb_data2 | {32{wb_rd3_decode[i]}} & wb_data3;
					mem_prf[i] <= {32{wb_rd1_decode[i]}} & wb_data1 | {32{wb_rd2_decode[i]}} & wb_data2 | {32{wb_rd3_decode[i]}}& wb_data3; 
				end
			end
		end
	end

	assign flush_request = !availiable;
	assign out_inst1_rd = inst1_rd_useful ? free_index1 : 0;
	assign out_inst2_rd = inst2_rd_useful ? !inst1_rd_useful ? free_index1 : free_index2 : 0;
	assign out_inst1_rs = rat1_inst1_rs;
	assign out_inst1_rt = rat1_inst1_rt;
	assign out_inst2_rs = rs_raw ? out_inst1_rd : rat1_inst2_rs;
	assign out_inst2_rt = rt_raw ? out_inst1_rd : rat1_inst2_rt;
	assign prf_inst1_rs = out_inst1_rs;
	assign prf_inst1_rt = out_inst1_rt;
	assign prf_inst2_rs = out_inst2_rs;
	assign prf_inst2_rt = out_inst2_rt;

	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0)begin
			for(int i=0; i<`REG_NUM; i++)begin
				rob_rat[i] <= i;
			end
			// for(int i=0; i<`PRF_SIZE - `REG_NUM; i++)begin
			// 	free_list[i] <= `REG_NUM + i;
			// end
			next_fail <= 0;
		end
		else begin
			next_fail <= branch_fail1 || branch_fail2;
			for(int i=0; i<`REG_NUM; i++)begin
				if(retire_origin_rd1_decode[i] | retire_origin_rd2_decode[i])begin
					rob_rat[i] <= {6{retire_origin_rd1_decode[i] & ~retire_origin_rd2_decode[i]}} & rob_rd1 | {6{retire_origin_rd2_decode[i]}} & rob_rd2;
				end
			end
		end
	end

	RAT rat1(
		.cpu_clk(cpu_clk),
		.cpu_rst_n(cpu_rst_n),
		.search1(inst1_rs),
		.search2(inst1_rt),
		.search3(inst2_rs),
		.search4(inst2_rt),
		.en1(stall ? 1'b0 : inst1_rd_useful),
		.en2(stall ? 1'b0 : inst2_rd_useful),
		.daddr1(inst1_rd),
		.daddr2(inst2_rd),
		.din1(out_inst1_rd),
		.din2(out_inst2_rd),
		.out1(rat1_inst1_rs),
		.out2(rat1_inst1_rt),
		.out3(rat1_inst2_rs),
		.out4(rat1_inst2_rt),
		// .out5(wb_rat_rd1),
		// .out6(wb_rat_rd2),
		.flush_en(next_fail),
		.flush(rob_rat)
	);
endmodule